using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class ShapeGeometry : BufferGeometry
    {
        public class Parameters
        {
            public ListEx<Shape> shapes;
            public int curveSegments;
        }

        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public ShapeGeometry(Shape shape = null, int curveSegments = 12) : this(shape == null ? null : new ListEx<Shape> { shape }, curveSegments) { }
        public ShapeGeometry(ListEx<Shape> shapes = null, int curveSegments = 12) : base()
        {
            if (shapes == null)
                shapes = new ListEx<Shape> { new Shape(new ListEx<Vector2> { new Vector2(0, 0.5), new Vector2(-0.5, -0.5), new Vector2(0.5, -0.5) }) };
            this.type = "ShapeGeometry";
            this.parameters = new Parameters
            {
                shapes = shapes,
                curveSegments = curveSegments
            };
            // buffers
            var indices = new ListEx<int>();
            var vertices = new ListEx<double>();
            var normals = new ListEx<double>();
            var uvs = new ListEx<double>();
            // helper variables
            var groupStart = 0;
            var groupCount = 0;
            // allow single and array values for "shapes" parameter
            for (int i = 0; i < shapes.Length; i++)
            {
                addShape(shapes[i]);

                this.addGroup(groupStart, groupCount, i); // enables MultiMaterial support
                groupStart += groupCount;
                groupCount = 0;
            }
            // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
            // helper functions
            void addShape(Shape shape)
            {
                var indexOffset = vertices.Length / 3;
                var points = shape.extractPoints(curveSegments);
                var shapeVertices = points.shape;
                var shapeHoles = points.holes;
                // check direction of vertices
                if (ShapeUtils.isClockWise(shapeVertices) == false)
                {
                    shapeVertices.Reverse();
                }
                for (int i = 0, l = shapeHoles.Length; i < l; i++)
                {
                    var shapeHole = shapeHoles[i];
                    if (ShapeUtils.isClockWise(shapeHole))
                    {
                        shapeHole.Reverse();
                        shapeHoles[i] = shapeHole;
                    }
                }
                var faces = ShapeUtils.triangulateShape(shapeVertices, shapeHoles);
                // join vertices of inner and outer paths to a single array
                for (int i = 0, l = shapeHoles.Length; i < l; i++)
                {
                    var shapeHole = shapeHoles[i];
                    shapeVertices = shapeVertices.Concat(shapeHole);
                }
                // vertices, normals, uvs
                for (int i = 0, l = shapeVertices.Length; i < l; i++)
                {
                    var vertex = shapeVertices[i];
                    vertices.Push(vertex.X, vertex.Y, 0);
                    normals.Push(0, 0, 1);
                    uvs.Push(vertex.X, vertex.Y); // world uvs
                }
                // indices
                for (int i = 0, l = faces.Length; i < l; i++)
                {
                    var face = faces[i];
                    var a = face[0] + indexOffset;
                    var b = face[1] + indexOffset;
                    var c = face[2] + indexOffset;
                    indices.Push(a, b, c);
                    groupCount += 3;
                }
            }
        }
        #endregion

        #region methods
        public ShapeGeometry copy(ShapeGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                shapes = source.parameters.shapes,
                curveSegments = source.parameters.curveSegments,
            };
            return this;
        }
        #endregion
    }
}
